package com.ztr.zdypost.custom;

import com.alibaba.fastjson.JSONObject;
import io.micrometer.core.instrument.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.ConvertUtils;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;

/**
 * <p>
 * 编写参数解析器
 * </p>
 */
@Slf4j
public class RequestPostSingleParamMethodArgumentResolver implements HandlerMethodArgumentResolver {

    private static final String POST = "post";
    private static final String APPLICATION_JSON = "application/json";

    /**
     * 判断当前处理器是否支持处理给定的方法参数。
     * 本方法用于判断传入的参数是否符合特定的注解条件，只有满足条件的参数才会被当前处理器处理。
     * 具体来说，它检查参数是否被@RequestPostSingleParam注解标记，如果是，则表示当前处理器支持该参数。
     * 这种机制允许灵活地指定哪些参数应该被特殊处理，从而实现对不同参数类型的泛化支持。
     *
     * @param parameter 需要检查的方法参数描述对象。
     * @return 如果当前处理器支持处理给定的参数，则返回true；否则返回false。
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        // 检查参数是否被@RequestPostSingleParam注解标记
        // 只处理带有@RequestPostSingleParam注解的参数
        return parameter.hasParameterAnnotation(RequestPostSingleParam.class);
    }


    /**
     * 根据请求参数解析方法参数。
     * 本方法专用于处理POST请求中以application/json格式提交的单个参数。
     * 它首先验证请求的类型和内容类型是否符合预期，然后绑定请求参数到方法参数上。
     *
     * @param parameter     方法参数信息，包含参数类型和名称等。
     * @param mavContainer  ModelAndViewContainer，用于存储视图相关信息，本方法中未使用。
     * @param webRequest    NativeWebRequest，封装了来自客户端的原始请求。
     * @param binderFactory WebDataBinderFactory，用于创建数据绑定器的工厂。
     * @return 绑定后的参数对象。
     * @throws Exception 如果解析过程中出现错误。
     */
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        // 从NativeWebRequest中获取HttpServletRequest实例
        HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
        // 获取请求的内容类型
        String contentType = Objects.requireNonNull(servletRequest).getContentType();

        // 验证请求的内容类型是否为application/json
        if (contentType == null || !contentType.contains(APPLICATION_JSON)) {
            log.error("《RequestPostSingleParam》 contentType需为【{}】", APPLICATION_JSON);
            throw new RuntimeException("《RequestPostSingleParam》 contentType需为application/json");
        }

        // 验证请求方法是否为POST
        if (!POST.equalsIgnoreCase(servletRequest.getMethod())) {
            log.error("《RequestPostSingleParam》 请求类型必须为post");
            throw new RuntimeException("《RequestPostSingleParam》 请求类型必须为post");
        }
        // 绑定请求参数到方法参数，并返回绑定后的对象
        return this.bindRequestParams(parameter, servletRequest);
    }


    /**
     * 绑定请求参数。
     * 该方法旨在从HTTP请求体中提取指定的参数，并将其转换为方法参数所期望的类型。
     * 使用了RequestPostSingleParam注解来指定参数的获取方式和是否必需。
     *
     * @param parameter      方法参数对象，包含参数的元数据。
     * @param servletRequest HTTP请求对象，从中提取请求体。
     * @return 经过转换的参数值，与方法参数类型匹配。
     */
    private Object bindRequestParams(MethodParameter parameter, HttpServletRequest servletRequest) {
        // 从方法参数上获取RequestPostSingleParam注解，用于配置参数的获取逻辑。
        RequestPostSingleParam requestPostSingleParam = parameter.getParameterAnnotation(RequestPostSingleParam.class);
        // 获取方法参数的类型，用于后续的类型转换。
        Class<?> parameterType = parameter.getParameterType();
        // 从HTTP请求中提取请求体内容。
        String requestBody = this.getRequestBody(servletRequest);
        // 将请求体内容解析为Map对象，用于按名称获取参数值。
        Map paramObj = JSONObject.parseObject(requestBody, Map.class);
        // 如果解析后的Map为空，则初始化一个空的JSONObject，确保后续操作的安全性。
        if (paramObj == null) {
            paramObj = new JSONObject();
        }
        // 根据注解配置或参数名称获取参数在请求体中的名称。
        String parameterName = StringUtils.isBlank(requestPostSingleParam.value()) ? parameter.getParameterName()
                : requestPostSingleParam.value();
        // 从解析后的请求体Map中获取指定名称的参数值。
        Object value = paramObj.get(parameterName);

        // 检查参数是否标记为必需，如果是，则验证其是否为空。
        if (requestPostSingleParam.required()) {
            if (value == null) {
                // 如果参数为空，记录错误日志并抛出异常。
                log.error("《RequestPostSingleParam》 require=true,参数【{}】不能为空!", parameterName);
                throw new RuntimeException("《RequestPostSingleParam》 " + parameterName + "不能为空!");
            }
        }

        // 将获取到的参数值转换为方法参数所期望的类型，并返回。
        return ConvertUtils.convert(value, parameterType);
    }


    /**
     * 从HttpServletRequest中获取请求体的内容。
     * 此方法用于处理POST请求中包含的主体数据，这些数据可能是表单数据、JSON数据或其他格式的数据。
     * 通过读取请求的输入流来获取主体内容，并将其作为字符串返回。
     *
     * @param servletRequest HttpServletRequest对象，用于获取请求体。
     * @return 请求体的内容作为字符串返回。
     * @throws RuntimeException 如果在读取请求体时发生IO异常，则抛出运行时异常。
     */
    private String getRequestBody(HttpServletRequest servletRequest) {
        // 初始化一个字符串构建器用于存储读取的请求体内容。
        StringBuilder stringBuilder = new StringBuilder();
        try {
            // 获取请求的输入流，并以字符方式读取内容。
            BufferedReader reader = servletRequest.getReader();
            // 创建一个字符数组缓冲区，用于一次读取一定量的数据。
            char[] buf = new char[1024];
            int length;
            // 循环读取输入流中的数据，直到没有更多数据可读。
            while ((length = reader.read(buf)) != -1) {
                // 将读取到的数据追加到字符串构建器中。
                stringBuilder.append(buf, 0, length);
            }
        } catch (IOException e) {
            // 如果在读取过程中发生IO异常，记录错误日志并抛出运行时异常。
            log.error("《RequestPostSingleParam》 读取流异常", e);
            throw new RuntimeException("《RequestPostSingleParam》 读取流异常");
        }
        // 返回构建好的请求体字符串。
        return stringBuilder.toString();
    }


}
